/**
 * @file    bin_ops_out_class.hh
 * @author  Ying-Po Liao <lrobin@neo.tamu.edu>
 * @version 1.0
 *
 * @section The header/implementations for "out of class" operator
 *          overloading, such as 
 *          array/tiny_vector/T op array/tiny_vector/T
 *          op can be +, -, *, /...
 *
 * CSCE-689 Design Using C++
 * Final Project
 */

#ifndef BIN_OPS_OUT_CLASS_H
#define BIN_OPS_OUT_CLASS_H

//////////////////////////////////////////////////////
//	operator overloading -- out of class
//	arr/vec/T op arr/vec/T
//	Type deduction
//////////////////////////////////////////////////////

template <typename l_type, typename r_type>
struct return_type_of_bin_ops
{
	struct sub_fail {};
	
	template <typename T, unsigned int N>
	static array<T, N>	deduce_acceptable_type( T, array<T, N> );
	template <typename T, unsigned int N>
	static array<T, N>	deduce_acceptable_type( array<T, N>, T );
	template <typename T, unsigned int N>
	static array<T, N>	deduce_acceptable_type( array<T, N>, array<T, N> );
	
	template <typename T, unsigned int N>
	static tiny_vector<T, N>	deduce_acceptable_type( T, tiny_vector<T, N> );
	template <typename T, unsigned int N>
	static tiny_vector<T, N> 	deduce_acceptable_type( tiny_vector<T, N>, T );
	template <typename T, unsigned int N>
	static tiny_vector<T, N> 	deduce_acceptable_type( tiny_vector<T, N>, tiny_vector<T, N> );

	static sub_fail	deduce_acceptable_type( ... ); //! SFINAE

	typedef typename std::remove_cv<l_type>::type l_no_const_T;
	typedef typename std::remove_cv<r_type>::type r_no_const_T;
	
	typedef decltype( 
		deduce_acceptable_type( declval<l_no_const_T>(), declval<r_no_const_T>() ) 
	)	type;

};

//////////////////////////////////////////////////////
//	operator overloading -- out of class
//	arr/vec/T op arr/vec/T
//	Declaration
//////////////////////////////////////////////////////

template <typename left_type, typename right_type>
auto operator+( const left_type& v1, const right_type& v2 ) -> 
	typename return_type_of_bin_ops<left_type, right_type>::type;

template <typename left_type, typename right_type>
auto operator-( const left_type& v1, const right_type& v2 ) -> 
	typename return_type_of_bin_ops<left_type, right_type>::type;

template <typename left_type, typename right_type>
auto operator*( const left_type& v1, const right_type& v2 ) -> 
	typename return_type_of_bin_ops<left_type, right_type>::type;

template <typename left_type, typename right_type>
auto operator/( const left_type& v1, const right_type& v2 ) -> 
	typename return_type_of_bin_ops<left_type, right_type>::type;

template <typename left_type, typename right_type>
auto operator%( const left_type& v1, const right_type& v2 ) -> 
	typename return_type_of_bin_ops<left_type, right_type>::type;

template <typename left_type, typename right_type>
auto operator^( const left_type& v1, const right_type& v2 ) -> 
	typename return_type_of_bin_ops<left_type, right_type>::type;

template <typename left_type, typename right_type>
auto operator&( const left_type& v1, const right_type& v2 ) -> 
	typename return_type_of_bin_ops<left_type, right_type>::type;

template <typename left_type, typename right_type>
auto operator|( const left_type& v1, const right_type& v2 ) -> 
	typename return_type_of_bin_ops<left_type, right_type>::type;

template <typename left_type, typename right_type>
auto operator>>( const left_type& v1, const right_type& v2 ) -> 
	typename return_type_of_bin_ops<left_type, right_type>::type;

template <typename left_type, typename right_type>
auto operator<<( const left_type& v1, const right_type& v2 ) -> 
	typename return_type_of_bin_ops<left_type, right_type>::type;

//////////////////////////////////////////////////////
//	operator overloading -- out of class
//	arr/vec/T op arr/vec/T
//	Implementation
//////////////////////////////////////////////////////

#define DEFINE_BIN_OPS_OUT_CLASS( OP, BIN_ASN_OP )			\
template <typename left_type, typename right_type>			\
auto operator OP( const left_type& v1, const right_type& v2 ) -> 	\
	typename return_type_of_bin_ops<left_type, right_type>::type	\
{									\
	typedef typename 						\
		return_type_of_bin_ops<left_type, right_type>::type	\
	return_type;							\
	typedef typename return_type::num_type T;			\
	return_type res;						\
	if ( std::is_same<left_type, T>::value )			\
	{								\
		res = v2;						\
		res BIN_ASN_OP v1;					\
	}								\
	else								\
	{								\
		res = v1;						\
		res BIN_ASN_OP v2;					\
	}								\
	return std::move( res );					\
}

DEFINE_BIN_OPS_OUT_CLASS( +, += )
DEFINE_BIN_OPS_OUT_CLASS( -, -= )
DEFINE_BIN_OPS_OUT_CLASS( *, *= )
DEFINE_BIN_OPS_OUT_CLASS( /, /= )
DEFINE_BIN_OPS_OUT_CLASS( %, %= )
DEFINE_BIN_OPS_OUT_CLASS( ^, ^= )
DEFINE_BIN_OPS_OUT_CLASS( &, &= )
DEFINE_BIN_OPS_OUT_CLASS( |, |= )
DEFINE_BIN_OPS_OUT_CLASS( >>, >>= )
DEFINE_BIN_OPS_OUT_CLASS( <<, <<= )

#endif //!< BIN_OPS_OUT_CLASS_H